#include "LocalPartitionCMP.h"
#include "output.h"

static address_t nil;


LocalPartitionCMP::LocalPartitionCMP(uint32_t chips,uint32_t threadsperchip,
                                     uint32_t MultiFELI_policy,uint32_t InterChipThreshold,
                                     uint32_t threads,uint32_t pageSize,
                                     uint32_t lineSize,uint32_t L0Size,
                                     uint32_t LMSize,uint32_t threshold,
                                     uint32_t invalidatePolicy,uint32_t victimPolicy)
    : CMP(threads, pageSize, lineSize, L0Size)
{
    _chips=chips;
    _threadsperchip=threadsperchip;
    _MultiFELI_policy=MultiFELI_policy;
    _LMSize=LMSize;
    _threshold=threshold;
    _InterChipThreshold=InterChipThreshold;
    _L0InvalidatePolicy=invalidatePolicy;
    _victimPolicy=victimPolicy;

    for(int i=0;i<threads;i++) {
        _LP[i] =  new LocalPartition (_LMSize,victimPolicy);
    }

    _LPHits = new uint32_t [_threads];
    _LPRemoteHitsOnChip = new uint32_t [_threads];
    _LPRemoteHitsOffChip = new uint32_t [_threads];
    _LPReplacementsOnChip = new uint32_t [_threads];
    _LPReplacementsOffChip = new uint32_t [_threads];

    reset();
}

LocalPartitionCMP::~LocalPartitionCMP()
{
    for (int i=0;i<_threads;i++) {
        delete  _LP[i];
    }
    delete [] _LPHits;
    delete [] _LPRemoteHitsOnChip;
    delete [] _LPRemoteHitsOffChip;
    delete [] _LPReplacementsOnChip;
    delete [] _LPReplacementsOffChip;
}

void
LocalPartitionCMP::reset()
{
    CMP::reset();

    for (int i=0;i<_threads;i++)
        _LP[i]->reset();

    memset(_LPHits,0,sizeof(uint32_t)*_threads);
    memset(_LPRemoteHitsOnChip,0,sizeof(uint32_t)*_threads);
    memset(_LPRemoteHitsOffChip,0,sizeof(uint32_t)*_threads);
    memset(_LPReplacementsOnChip,0,sizeof(uint32_t)*_threads);
    memset(_LPReplacementsOffChip,0,sizeof(uint32_t)*_threads);
}

void
LocalPartitionCMP::executeMigrateonMutex(Memory * mem,Trace * trace)
{
    uint32_t totalAccesses=trace->getAccessSize(0);
    uint32_t tArray[]= {100,250,500,5000,10000,100000,250000,500000,1000000,3000000};
    uint32_t maxSteps=9;
    char dirname[1024];

    trace->getDirname(dirname);
    uint32_t migrate = mem->getMigrate();
    bool skipwrite = trace->getSkipwrite();
    for (uint32_t j=0;_threshold<totalAccesses && j<maxSteps;j++) {
        _threshold=tArray[j];
        mem->reset();
        //preAllocate(mem,trace,policy);
        executeLPSimulation(mem,trace,POLICY_PROFILE_DYNAMIC);
        //outputResults(dirname,mem,POLICY_PROFILE_DYNAMIC,_threshold,skipwrite);
        //outputMigrationResults(dirname,mem,POLICY_PROFILE_DYNAMIC,_threshold,skipwrite);
    }

    /* Activate barriers*/
    migrate=MIGRATE_MUTEX;
    mem->reset();
    //preAllocate(mem,trace,policy);
    executeLPSimulation(mem,trace,POLICY_PROFILE_DYNAMIC);
    outputResults(this,dirname,mem,POLICY_PROFILE_DYNAMIC,_threshold,skipwrite);
    //outputMigrationResults(dirname,mem,POLICY_PROFILE_DYNAMIC,_threshold,skipwrite);
    //outputWorkingSet(dirname,mem,0);

}
    void
LocalPartitionCMP::execute(Memory * mem,Trace * trace,ConfigParameter conf)
{
    int i=0;
    std::cout << "LocalPartition CMP: option="<< conf.getLMOption() << std::endl;
    switch (conf.getLMOption()) {
        case 0:
                    executeMigrateonMutex(mem,trace);
                    break;
        case 1:
                mem->reset();
                executeLPSimulation(mem,trace,POLICY_WORKINGSET);
                break;
        case 4:
            for (i=0;i<=10;i++) {
                trace->reset();
                mem->reset();
                this->reset();
                _threshold=pow(2,i);
                executeLPSimulation(mem,trace,POLICY_FELI);
                outputThresholdLMResults(this,mem,trace,conf);
            }
            break;
        case 5:
            trace->reset();
            mem->reset();
            executeLPSimulation(mem,trace,POLICY_FELI);
            outputThresholdLMResults(this,mem,trace,conf);
            break;
        case 6:
            trace->reset();
            mem->reset();
            executeLPSimulation(mem,trace,POLICY_MULTIFELI);
            outputThresholdLMResults(this,mem,trace,conf);
           break;
        default:
           std::cout<< "Unrecognized  LMOption"<<std::endl;
           break;
    }
}


void
LocalPartitionCMP::preAllocate(Memory * mem,Trace * trace,uint32_t policy)
{
    uint32_t LM_full=0;
    uint32_t end=0;
    bool * finished=NULL;
    uint32_t * index=NULL;

    index = new uint32_t[_threads];
    finished = new bool[_threads];

    memset(index,0,_threads*sizeof(uint32_t));
    memset(finished,false,_threads*sizeof(bool));

    if (index==NULL ||finished==NULL) {
        FATAL("Unable to allocate memory in preAllocate");
    }

    switch (policy) {
        case POLICY_PROFILE:
        case POLICY_PROFILE_DYNAMIC:
            while (LM_full!=_threads && end!=_threads) {
                for (int i=0;i<_threads;i++) {
                    // allocate non full LM
                    if (!finished[i]) {
                        /* allocate 1 page per thread */
                        uint32_t location=MAINMEMORY;
                        while (location!=i &&
                                mem->getNumPages(i)<_LMSize &&
                                trace->vectorthreadSize(i)>index[i] ) {
                            uint32_t page=trace->getMaxAccessPage(i,index[i]);
                            location=mem->getLocation(page);
                            if (location==MAINMEMORY) {
                                mem->setPage(page,i);
                                location=i;
                            }
                            index[i]++;
                        }
                    }
                }
                /*  check if there is still space in LM*/
                LM_full=end=0;
                for (int i=0;i<_threads;i++) {
                    if (mem->getNumPages(i)>=_LMSize)
                        LM_full++;
                    if (trace->vectorthreadSize(i)==index[i]) {
                        finished[i]=true;
                        end++;
                    }
                }
            }
            break;
    }

    delete index;
    delete finished;

}



uint32_t
LocalPartitionCMP::policyFirst(Memory * mem, uint32_t thread,uint32_t page,uint32_t access)
{
    uint32_t lat=0;

    if (mem->getLocation(page)==MAINMEMORY &&
            mem->getNumPages(thread)<_LMSize) {
        //location=thread means LM
        lat=mem->migrate(page,thread,thread);
    }
    return  mem->access(page,thread);
}


uint32_t
LocalPartitionCMP::policyFirstThreshold(Memory * mem, uint32_t thread,uint32_t page,uint32_t access)
{
    uint32_t lat=0;

    if (mem->getLocation(page)==MAINMEMORY &&
            mem->getNumPages(thread)<_LMSize) {
        lat+=mem->migrate(page,thread,thread);
    }
    return  mem->access(page,thread);
}

uint32_t
LocalPartitionCMP::policyProfile(Memory * mem, Trace *trace,uint32_t thread,uint32_t page,uint32_t access)
{
    return  mem->access(page,thread);
}


uint32_t
LocalPartitionCMP::policyProfileDynamic (Memory * mem,  Trace *trace,uint32_t thread,uint32_t page,uint32_t access)
{
    /* Migration only done at thread 0 to skip */
    switch(mem->getMigrate()) {
        case MIGRATE_TH:
            if  (thread==0 && access>0 && access%_threshold==0) {
                std::cout << "Dynamic Migrate :"<< access <<std::endl;
                mem->do_ProfileDynamic_migrate();
                // sched_migrations
            }
            return  mem->access(page,thread);
            break;
        case MIGRATE_MUTEX:
            if (thread==0 && access>0 && trace->isMutex(thread,access)) {
                std::cout << "Dynamic Migrate :"<< access <<std::endl;
                mem->do_ProfileDynamic_migrate();
                // sched_migrations
            }
            return mem->access(page,thread);
            break;
        case MIGRATE_MIX:
            if  (thread==0 && access>0 &&
                (access%_threshold==0 ||
                trace->isMutex(thread,access))) {
                    std::cout << "Dynamic Migrate :"<< access <<std::endl;
                    mem->do_ProfileDynamic_migrate();
                // sched_migrations
            }
            return mem->access(page,thread);
            break;

    }
    return 0;
}

uint32_t
LocalPartitionCMP::policyWorkingSet(Memory * mem, Trace *trace, uint32_t thread, uint32_t page,uint32_t access)
{
    static  int threshold = (trace->getgAccesses()/_threads)/10;

    // Generate working set in 10 steps to show time evolution
    if (thread==0 && access%threshold==0) {
        uint32_t step = access/threshold;
        char dirname[1024];
        trace->getDirname(dirname);
        outputWorkingSet(dirname,mem,step);
        mem->reset();
    }
    return mem->access(page,thread);
}

void
LocalPartitionCMP::invalidateL0(uint64_t replaced,uint32_t currentThread,
                                bool isL0cached)
{
    address_t invAddr=0;
    uint32_t size=0;

    switch (_L0InvalidatePolicy) {
        case L0_PARTIAL_INVALIDATE:
                invAddr=replaced*_pageSize;
                size=_pageSize/CMP::getLineSize();
                for (int i=0;i<size;i++) {
                    _L0Cache[currentThread]->flush(invAddr);
                    invAddr+=CMP::getLineSize();
                }
            break;
        case L0_FILTER_INVALIDATE:
            if (isL0cached) {
                _LP[currentThread]->resetL0Filter();
                _L0Cache[currentThread]->flushAll();
            }
            break;
        case L0_ALL_INVALIDATE:
            _L0Cache[currentThread]->flushAll();
            break;
    }
}

uint32_t
LocalPartitionCMP::FELI_localChipAccess(Memory *mem,uint64_t & addr,address_t &physical,
                     uint32_t & page,uint32_t thread,uint32_t opcode)
{
    address_t evict;
    Directory dir(1);

    if (_L0Size>0) {
        if (_L0Cache[thread]->find(addr)) {
            if (_L0Cache[thread]->read(addr,evict,dir,dir,nil,thread)) {
                _L0CacheHits[thread]++;
                return L0CACHE;
            }
        }
        _L0CacheMisses[thread]++;
    }

    // Get Location and Access Data
    uint32_t ret = _tlb[thread]->translate(addr,physical);
    if (ret==TLB_MISS) {
        // we use the same translation
        _tlb[thread]->insert(addr,addr);
        return  UNDEFINED_LOCATION;
    }
    addr = (physical.get()/_pageSize)*_pageSize + (addr%_pageSize);
    page = physical.get()/_pageSize;

    /* Get Location and Update Access Counter */
    uint32_t location=mem->access(page,thread);

    // TLB HIT - Check location and increase counters
    if (location==thread) {
        if (_L0Size>0) {
            _L0CacheMisses[thread]++;
            _L0Cache[thread]->writeAllocate(addr,evict,thread,nil);
        }
        if (opcode==LOAD_INST)   _LP[thread]->read(page,false);
        else                     _LP[thread]->write(page,false);
        _LPHits[thread]++;
        return location;
    }
    return location;
}

uint32_t
LocalPartitionCMP::policyFELI(Memory * mem, uint32_t thread, uint64_t addr,
        uint32_t access, uint32_t opcode)
{
    address_t physical;
    uint32_t  replacedPage=0;
    uint32_t location;
    uint32_t page;

    location=FELI_localChipAccess(mem,addr,physical,page,thread,opcode);
    if (location==thread || location==L0CACHE)
        return thread;

    // Remote LP
    if (location!=thread && location<(_chips*_threadsperchip)) {
        _LPRemoteHitsOnChip[thread]++;
        if (opcode==LOAD_INST)   _LP[thread]->read(page,true);
        else {
            bool isL0cached= _LP[thread]->write(page,true);
            if (_L0Size>0) {
                invalidateL0(physical.get(),thread,isL0cached);
            }
        }
        return location;
    }

    // There's no hit
    // Decide if we need replacement
    if (mem->getPageAccesses(page,thread) > _threshold) {
        // check if there is a replacement
        if ( _LP[thread]->insert(page, replacedPage)) {
            mem->setLocation(replacedPage ,MAINMEMORY);
            mem->resetPageAccesses(replacedPage,thread);
            if (_L0Size>0)
                invalidateL0(replacedPage,thread,true);
        }
        mem->setLocation(page,thread);
        _LPReplacementsOnChip[thread]++;
        mem->resetPageAccesses(page,thread);
   }
    _mainMemoryAccesses[thread]++;
    return location;
}

bool
LocalPartitionCMP::isLocalChipLocation(uint32_t location,uint32_t thread)
{
       return (location/_threadsperchip)==(thread/_threadsperchip);
}

void
LocalPartitionCMP::migratePage(Memory *mem,uint32_t page,uint32_t location,
                               uint32_t thread)
{
    uint32_t  replacedPage=0;

    // check if there is a replacement
    if ( _LP[thread]->insert(page, replacedPage)) {
        mem->setLocation(replacedPage ,MAINMEMORY);
        mem->resetPageAccesses(replacedPage,thread);
        if (_L0Size>0)
            invalidateL0(replacedPage,thread,true);
    }

    if (_L0Size>0)
        invalidateL0(page,location,true);
    _LP[location]->remove(page);
    mem->setLocation(page,thread);
    mem->resetPageAccesses(page,thread);
    _LPReplacementsOffChip[thread]++;

}


uint32_t
LocalPartitionCMP::getChipAccesses(Memory *mem, uint32_t thread,uint32_t page)
{
    uint32_t total=0;
    uint32_t chip=thread/_threadsperchip;

    for (int i=0;i<_threadsperchip;i++) {
        total+=mem->getPageAccesses(page,chip+i);
    }
    return total;

}

uint32_t
LocalPartitionCMP::MultiFELINormal(Memory * mem,uint32_t location,address_t physical,
        uint32_t page, uint32_t thread,uint32_t opcode)
{
    bool migrate=false;

    // Check Access Counters using Normal Policy
    uint32_t diff=getChipAccesses(mem,location,page)-
                  getChipAccesses(mem,thread,page);
    if (diff > _InterChipThreshold) {
        migrate=true;
    }

    // Migrate to other LP
    if (migrate) {
        migratePage(mem,page,location,thread);
        location=thread;
    }

    return location;
}

    uint32_t
LocalPartitionCMP::MultiFELIFroze(Memory * mem,uint32_t location,address_t physical,
        uint32_t page, uint32_t thread,uint32_t opcode)
{
    bool migrate=false;

    // Check Access Counters using Normal Policy
    uint32_t diff=getChipAccesses(mem,location,page)-
                  getChipAccesses(mem,thread,page);
    if (diff > _InterChipThreshold) {
        migrate=true;
    }

    // Migrate to other LP
    if (migrate) {
        migratePage(mem,page,location,thread);
        // Put negative value to accesses so page can't be migrated
        mem->resetPageAccesses(page);
        location=thread;
    }
    return location;
}

uint32_t
LocalPartitionCMP::MultiFELIPivot(Memory * mem,uint32_t location,address_t physical,
                              uint32_t page, uint32_t thread,uint32_t opcode)
{
    return location;
}



uint32_t
LocalPartitionCMP::FELI_remoteChipAccess(Memory * mem,uint32_t location,address_t physical,
                      uint32_t page, uint32_t thread,uint32_t opcode)
{
    _LPRemoteHitsOffChip[thread]++;
    switch (_MultiFELI_policy) {
        case MULTIFELI_NORMAL:
                    return MultiFELINormal(mem,location,physical,page,thread,opcode);
                    break;
        case MULTIFELI_FROZE:
                    return MultiFELIFroze(mem,location,physical,page,thread,opcode);
                    break;
        case MULTIFELI_PIVOT:
                    return MultiFELIPivot(mem,location,physical,page,thread,opcode);
                    break;
    }
    return location;
}

uint32_t
LocalPartitionCMP::policyMultiFELI(Memory * mem, uint32_t thread, uint64_t addr,
        uint32_t access, uint32_t opcode)
{
    address_t physical;
    uint32_t replacedPage=0;
    uint32_t page;

    uint32_t location = FELI_localChipAccess(mem,addr,physical,page,thread,opcode);

    // Local core Accesses
    if (location==L0CACHE || location==thread) {
        return location;
    }

    // Remote Local Partition Access
    if (location!=thread && location!=MAINMEMORY &&
        location<(_chips*_threadsperchip)) {
        if (isLocalChipLocation(location,thread)) {
            _LPRemoteHitsOnChip[thread]++;
            if (opcode==LOAD_INST)
                _LP[thread]->read(page,true);
            else {
                bool isL0cached= _LP[thread]->write(page,true);
                if (_L0Size>0) {
                    invalidateL0(physical.get(),thread,isL0cached);
                }
            }
            return location;
        }
        else {
            return FELI_remoteChipAccess(mem,location,physical,page,thread,opcode);
        }
    }

    // There's no hit
    /* Decide if we need replacement */
    if (mem->getPageAccesses(page,thread) > _threshold) {
        // check if there is a replacement
        if ( _LP[thread]->insert(page, replacedPage)) {
            mem->setLocation(replacedPage ,MAINMEMORY);
            mem->resetPageAccesses(replacedPage,thread);
            if (_L0Size>0)
                invalidateL0(replacedPage,thread,true);
        }
        mem->setLocation(page,thread);
        _LPReplacementsOnChip[thread]++;
        mem->resetPageAccesses(page,thread);
   }
    _mainMemoryAccesses[thread]++;
    return location;
}


    uint32_t
LocalPartitionCMP::policyL1Cache(Memory * mem,  uint32_t thread, uint32_t page,uint32_t access)
{
    std::pair< uint32_t ,uint32_t > replaced;

    if (!_LP[thread]->find(page)) {
        replaced.first = _LP[thread]->insert(page, replaced.second);
    }
    else {
        _LPHits[thread]++;
    }
    return mem->access(page,thread);
}

uint32_t
LocalPartitionCMP::policyL2Cache(Memory * mem,  uint32_t thread, uint32_t page,uint32_t access)
{
    std::pair< uint32_t ,uint32_t > replaced;

    if (!_LP[0]->find(page)) {
       replaced.first = _LP[0]->insert(page,  replaced.second);
    }
    else {
       _LPHits[thread]++;
    }
    return mem->access(page,thread);
}



uint32_t
LocalPartitionCMP::getLocation(Memory * mem, Trace *trace,uint32_t thread,
        uint64_t addr,uint32_t access,uint32_t policy,uint32_t opcode)
{
    uint32_t page=addr/_pageSize;
    switch (policy) {
        case POLICY_PROFILE:
                        // allocate the most accessed page in the corresponding
                        // LM
                        return policyProfile(mem,trace,thread,page,access);
                        break;
        case POLICY_PROFILE_DYNAMIC:
                        return policyProfileDynamic(mem,trace,thread,page,access);
                        break;
        case POLICY_FIRST:
                        // allocate the first accessed page in the corresponding
                        // LM
                        return policyFirst(mem,thread,page,access);
                        break;
        case POLICY_FIRST_THRESHOLD:
                        // FirstTouch Threshold
                        return policyFirstThreshold(mem,thread,page,access);
                        break;
        case POLICY_WORKINGSET:
                        return policyWorkingSet(mem,trace,thread,page,access);
                        break;
        case POLICY_FELI:
                        return policyFELI(mem,thread,addr,access,opcode);
                        break;
        case POLICY_MULTIFELI:
                        return policyMultiFELI(mem,thread,addr,access,opcode);
                        break;
        case POLICY_L1CACHE:
                        return policyL1Cache(mem,thread,page,access);
                        break;
        case POLICY_L2CACHE:
                        return policyL2Cache(mem,thread,page,access);
                        break;
    }
    return 0;
}

// Simulate Parallell execution following policy
bool
LocalPartitionCMP::executeLPSimulation(Memory *mem,Trace *trace,uint32_t policy)
{

    uint64_t address=0;
    uint32_t opcode=0;
    uint32_t access[32];
    uint32_t tidFinished = 0;

    memset(access,0,sizeof(uint32_t)*_threads);

    while (tidFinished < _threads) {
        tidFinished=0;
        // one Access per Thread
        for (uint32_t tid = 0; tid < _threads; tid++) {
            if (trace->getMemoryAccess(tid,access[tid],address,opcode)) {
                uint32_t loc = getLocation(mem,trace,tid,address,
                        access[tid],policy,opcode);
                if (loc!=UNDEFINED_LOCATION) {
                    access[tid]++;
                }
            }
            else {
                tidFinished++;
            }
        }
    }
    return true;
}

